﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data;

namespace AFCEPF.BankApplication.DAL
{
    /// <summary>
    /// Base class for all mapped objects.
    /// </summary>
    public abstract class DataMapper
    {

        private int _id;
        private const int UNDEFINED_ID = -1;

        private readonly IDictionary<string, MappedProperty> _mappedProperties;
        private readonly AbstractDbAccessor _dbAccessor;


        /***************************************************
         * Constructor(s)
         **************************************************/
        

        /// <summary>
        /// Ctor that will be called to instantiate
        /// a class that does not come from the database.
        /// </summary>
        protected DataMapper() : this(UNDEFINED_ID)
        {}

        /// <summary>
        /// Ctor that will be called to instantiate
        /// a class that maps to the given id value.
        /// </summary>
        /// <param name="id">The id.</param>
        protected DataMapper(int id)
        {
            _id = id;

            // Let's create the dictionary of MappedProperty..
            _mappedProperties = new Dictionary<string, MappedProperty>();
            
            // On ira remplir le dictionnaire avec 
            // des objets MappedProperty représentant 
            // les propriétés mappées..
            RefreshMappedProperties();

            // On instancie le bon type de DbAccessor 
            // via notre factory..
            _dbAccessor = DbAccessorFactory.Create(this);

            // If the current instance has an Id,
            // read the instance from the database..
            if ( _id != UNDEFINED_ID )
            {
                Read();
            }

        }




        /***************************************************
         * Methods
         **************************************************/



        /// <summary>
        /// Gets the name of the mapped table.
        /// </summary>
        /// <returns>the name of the mapped table.</returns>
        public string GetTableName()
        {
            // Let's get the TableAttribute that
            // is decorating the current instance..
            var tableAttribute = this.GetType().GetUniqueAttribute<TableAttribute>();
            return tableAttribute.TableName;
        }

        /// <summary>
        /// Refreshes the mapped properties.
        /// </summary>        
        protected void RefreshMappedProperties()
        {
            // on obtient le type de l'instance 
            // en cours..
            var type = this.GetType();


            // On va chainer des valeurs de l'enum BindingFlags
            // afin d'accèder à tous les membres
            // sauf les membres statiques..
            var flags =             BindingFlags.Public     |
                                    BindingFlags.NonPublic  |
                                    BindingFlags.Instance;

            // on va parcourir toutes les propriétés 
            // de l'instance en cours..
            foreach (PropertyInfo property in type.GetProperties(flags))
            {
                // si la propriété en cours est bien
                // décorée avec un FieldAttribute..
                //if (ReflectionHelper.IsDecoratedWith<FieldAttribute>(property))
                if (property.IsDecoratedWith<FieldAttribute>())
                {
                    // On crée un nouvel objet MappedProperty
                    // qui représente notre propriété mappée..
                    var prop = new MappedProperty(property, this);

                    // on l'ajoute à notre dictionnaire MappedProperties..
                    _mappedProperties.Add(property.Name, prop);
                }



            }


        }


        /// <summary>
        /// Deletes this instance from the database.
        /// </summary>
        /// <returns>true on success.</returns>
        public virtual bool Delete()
        {
            var result = false;

            if (_id == UNDEFINED_ID)
            {
                var message = string.Format("The Key {0} was not found in the table {1}",
                                                Id,
                                                GetTableName());
                throw new KeyNotFoundException(message);
            }

            result = DbAccessor.Delete();

            // Reset the instance's Id property
            // back to default value, to indicate
            // that this instance is not contained
            // in the database anymore.
            if (result)
            {
                var prop = GetPrimaryKeyProperty();
                prop.PropertyValue = UNDEFINED_ID;
            }

            return result;
        }


        /// <summary>
        /// Saves this instance in the database.
        /// </summary>
        /// <returns>true on success.</returns>
        public virtual bool Save()
        {
            bool result = false;

            // Gets the MappedProperty that 
            // represents the primary key..
            var primaryKeyProperty = GetPrimaryKeyProperty();

            // If the current instance does not 
            // comes from the database, let's INSERT !
            if ((int)primaryKeyProperty.PropertyValue == UNDEFINED_ID)
            {
                int lastGeneratedId = UNDEFINED_ID;

                // performs the INSERT and get back
                // the last generated Id value..
                lastGeneratedId = DbAccessor.Insert();

                result = (lastGeneratedId > 0);

                // On operation success, set the primary
                // key value to the last Id generated 
                // by the INSERT..
                if (result)
                {
                    primaryKeyProperty.PropertyValue = lastGeneratedId;
                }
            }
            // If the current instance 
            // comes from the database, let's UPDATE ! 
            else
            {
                result = DbAccessor.Update();
            }

            return result;
        }


        /// <summary>
        /// Reads this instance from the database
        /// and the record values to the current
        /// instance's mapped properties..
        /// </summary>
        /// <returns>true on success</returns>
        protected void Read()
        {
            // Let's get the DataTable containing
            // the record for this instance..
            DataTable table = DbAccessor.Select();

            // Check the DataTable for consistency..
            int rowCount = table.Rows.Count;

            // on record not found for the given Id..
            if (rowCount == 0)
            {
                string message = string.Format("The Key {0} was not found in the table {1}",
                                                Id,
                                                GetTableName());
                throw new KeyNotFoundException(message);
            }
            // on several records found for 
            // the given Id (malformed database)..
            if (rowCount > 1)
            {
                string message = string.Format("The Key {0} is not unique in the database",
                                               _id.ToString());
                throw new DuplicateKeyFoundException(message);
            }

            // Map DataTable's Row values to 
            // current instance's property values.
            foreach (DataColumn col in table.Columns)
            {
                foreach (var mappedProperty in _mappedProperties.Values.Where(mappedProperty => mappedProperty.PropertyFieldName == col.Caption && 
                                                                                                           !mappedProperty.IsPrimaryKey))
                {
                    mappedProperty.PropertyValue = table.Rows[0][col.Caption];
                }
            }
        }


        /// <summary>
        /// Gets MappedProperty that 
        /// represents the primary key property.
        /// </summary>
        /// <returns>A MappedProperty that 
        /// represents the primary key</returns>
        private MappedProperty GetPrimaryKeyProperty()
        {
            var query = (from prop in _mappedProperties.Values
                         where prop.IsPrimaryKey
                         select prop).First();
            return query;
        }
        
        /***************************************************
         * Properties
         **************************************************/



        /// <summary>
        /// Represents the primary key for all derived
        /// classes.
        /// </summary>
        [Field("Id", IsPrimaryKey = true)]
        public int Id
        {
            get { return _id; }
            protected internal set { _id = value; }
        }


        /// <summary>
        /// Gets the db accessor.
        /// </summary>
        public AbstractDbAccessor DbAccessor
        {
            get { return _dbAccessor; }
        }

        /// <summary>
        /// Gets the mapped properties.
        /// </summary>
        public IDictionary<string, MappedProperty> MappedProperties
        {
            get { return _mappedProperties; }
        }

    }
}
